package jp.business.utils.util;

import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import static org.springframework.beans.BeanUtils.getPropertyDescriptor;
import static org.springframework.beans.BeanUtils.getPropertyDescriptors;

/**
 * 对象拷贝的方法
 *
 * @author 云野
 */
public class BeanUtils {

  /**
   * 将source的非空属性，拷贝到targer对象中 （修改spring属性拷贝，只拷贝不为空的属性）
   *
   * @param source 源对象
   * @param target 目标对象
   * @param editable 对象类型
   * @param ignoreProperties 忽略字段
   */
  public static void copyProperties(
      Object source, Object target, Class<?> editable, String... ignoreProperties)
      throws BeansException {

    Assert.notNull(source, "Source must not be null");
    Assert.notNull(target, "Target must not be null");

    Class<?> actualEditable = target.getClass();
    if (Objects.nonNull(editable)) {
      if (!editable.isInstance(target)) {
        throw new IllegalArgumentException(
            "Target class ["
                + target.getClass().getName()
                + "] not assignable to Editable class ["
                + editable.getName()
                + "]");
      }
      actualEditable = editable;
    }
    PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
    List<String> ignoreList = (ignoreProperties != null) ? Arrays.asList(ignoreProperties) : null;

    for (PropertyDescriptor targetPd : targetPds) {
      Method writeMethod = targetPd.getWriteMethod();
      //  判断条件
      boolean writeMethodB =
          Objects.nonNull(writeMethod)
              && (ignoreProperties == null || (!ignoreList.contains(targetPd.getName())));

      if (writeMethodB) {
        PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
        if (Objects.nonNull(sourcePd)) {
          Method readMethod = sourcePd.getReadMethod();
          boolean readMethodB =
              Objects.nonNull(readMethod)
                  && ClassUtils.isAssignable(
                      writeMethod.getParameterTypes()[0], readMethod.getReturnType());
          if (readMethodB) {
            try {
              if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                readMethod.setAccessible(true);
              }
              Object value = readMethod.invoke(source);
              // 只拷贝不为null的属性
              if (Objects.nonNull(value)) {
                if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                  writeMethod.setAccessible(true);
                }
                writeMethod.invoke(target, value);
              }
            } catch (Throwable ex) {
              throw new FatalBeanException(
                  "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
            }
          }
        }
      }
    }
  }
}
